#include "cache.h"

/* Author: Chaomin YU
 * key idea:
 * linked list cache organization
 * formatted uri as tag
 * header+body   as content value
 * using LRU via moving the least recent used block to the tail
 * choose the first element to be victimed 
 * using posix read/write lock to cache visiting and updating thread-safe
 */

/* read/write lock */
static pthread_rwlock_t cachelock;

static int totalsize = 0;

/* visit_cache: visit the cache for uri
 *              determine if it exists in the cache
 *              if so return the corresponsing Object and write to client
 *              otherwise cache miss 
 * add read/write lock to make it thread-safe
 */
struct Object *visit_cache(char* uri){

     int error;
     /* set a read lock */
     if ((error = pthread_rwlock_rdlock(&cachelock))){
          return NULL;
     }
     struct Block *block; 
     if(is_empty())
     {
          if ((error = pthread_rwlock_unlock(&cachelock))){
              pthread_rwlock_destroy(&cachelock);
              return NULL;
          }
          return NULL;
     }
     for(block = get_head(); block != NULL; block = block->next){
         if(strcmp(block->uri, uri) == 0){
             break;
         }
     }
     /* unlock the read lock */
     if ((error = pthread_rwlock_unlock(&cachelock))){
          pthread_rwlock_destroy(&cachelock);
          return NULL;
     }

     if (block == NULL)
         return NULL; 

     /* set a write lock -- for move_to_tail() */
     if ((error = pthread_rwlock_wrlock(&cachelock))){
         return NULL;
     }
     move_to_tail(block);
     struct Object *object = block->object;
     /* unlock the write lock */
     if ((error = pthread_rwlock_unlock(&cachelock))){
         pthread_rwlock_destroy(&cachelock);
         return NULL;
     }
     
     return object;
}

/* update_cache: when cache miss
 *       a. if the object size > MAX_OBJECT_SIZE, do nothing
 *       b. else
 *             - 1. if cache is not full add to cache list
 *             - 2. if there's no enough space, exchange one using LRU
 * add read/write lock to make it thread-safe
 */

int update_cache(char *uri, struct Object *object){
     int error;
     /* write lock */
     if ((error = pthread_rwlock_wrlock(&cachelock))){
          return -1;
     }
 
     struct Block *block;
     struct Block *new_block = Malloc(sizeof(struct Block));
     new_block->uri = uri;
     new_block->object = object;

     if(is_full(totalsize + object->bodysize)){
         /* cache miss and replace one out */
         for(block = get_head(); block != NULL; block = block->next){
             if(!is_full(totalsize + object->bodysize - block->object->bodysize)){
                 remove_node(block);
                 add_node(new_block);
                 break;
             }
             else{
                 remove_node(block);
             }
         }
         if (block == NULL){
             if ((error = pthread_rwlock_unlock(&cachelock))){
                  pthread_rwlock_destroy(&cachelock);
                  return -1;
             }
             return 0;
         }
     }
     else{
         add_node(new_block);
     }

     /* unlock the write lock */
     if ((error = pthread_rwlock_unlock(&cachelock))){
          pthread_rwlock_destroy(&cachelock);
          return -1;
     }
     
     return 1;
}

/* init_cache - init cache_list and cachelock */
void init_cache()
{
    cache_list = Calloc(1, sizeof(struct Block));
    struct Block * block = cache_list;
    block->next = NULL;
    block->uri = NULL;
    block->object = NULL;
    int error;
    if((error=init_container()))
        printf("initerror : %d", error);
    return;
}


/* is_full: return 1 if size > MAX_CACHE_SIZE */
int is_full(int size){
    return (size > MAX_CACHE_SIZE);
}


/* whether the cache list is empty */
int is_empty(){
    return (get_head() == NULL);
}

/* get the first element of cache_list
 * if it is empty , first should be NULL
 */
struct Block * get_head(){
    return cache_list->next;
}

/* get the last element of cache_list */
struct Block * get_tail(){
    struct Block *block;
    if(is_empty())
        return NULL;
    for(block = cache_list; block->next != NULL; block = block->next);
    return block;
}

/* add a new block to the list tail pos */
void add_node(struct Block *block)
{
    struct Block *tail = get_tail();
    if(is_empty())
        cache_list->next = block;
    else
        tail->next = block;
    block->next = NULL;
    totalsize += block->object->bodysize;
    return;
}

/* remove a block and free the space*/
void remove_node(struct Block *block)
{
    struct Block *p;
    for(p = cache_list; p != NULL; p = p->next){
        if(p->next == block)
            break;
    }
    totalsize -= block->object->bodysize;
    p->next = block->next;
    Free(block->uri); 
    Free(block->object);
    Free(block);
    return ;
}

/* when one block is touched, it is moved to head to ensure LRU */
void move_to_tail(struct Block *block){
    struct Block *p;
    for(p = cache_list; p != NULL; p = p->next){
        if(p->next == block)
            break;
    }
    
    p->next = block->next;
   
    add_node(block);
    return;
}
    
/* pack the uri to format using host,path and port */
char *pack_uri(char *host, char *path, int port){
    char * uri = Calloc(MAXLINE, 1);
    sprintf(uri, "http://%s:%d%s", host, port, path);
    return uri;
}

/* init the cachelock */
int init_container(void){
    return pthread_rwlock_init(&cachelock, NULL);
}



